#include "hzpch.h"
#include "SceneSerializer.h"
#include "SourceCode/Scene/Entity.h"
#include "SourceCode/Scene/Components.h"

#include <yaml-cpp/yaml.h>

namespace YAML
{
	template<>
	struct convert<glm::vec2>
	{
		static Node encode(const glm::vec2& rhs)
		{
			Node node;
			node.push_back(rhs.x);
			node.push_back(rhs.y);

			return node;
		}

		static bool decode(const Node& node, glm::vec2& rhs)
		{
			if (2 != node.size() || !node.IsSequence())
			{
				return false;
			}

			rhs.x = node[0].as<float>();
			rhs.y = node[1].as<float>();

			return true;
		}
	};

	template<>
	struct convert<glm::vec3>
	{
		static Node encode(const glm::vec3& rhs)
		{ 
			Node node;
			node.push_back(rhs.x);
			node.push_back(rhs.y);
			node.push_back(rhs.z);

			return node;
		}

		static bool decode(const Node& node, glm::vec3& rhs)
		{
			if (3 != node.size() || !node.IsSequence())
			{
				return false;
			}

			rhs.x = node[0].as<float>();
			rhs.y = node[1].as<float>();
			rhs.z = node[2].as<float>();

			return true;
		}
	};

	template<>
	struct convert<glm::vec4>
	{
		static Node encode(const glm::vec4& rhs)
		{
			Node node;
			node.push_back(rhs.x);
			node.push_back(rhs.y);
			node.push_back(rhs.z);
			node.push_back(rhs.w);

			return node;
		}

		static bool decode(const Node& node, glm::vec4& rhs)
		{
			if (4 != node.size() || !node.IsSequence())
			{
				return false;
			}

			rhs.x = node[0].as<float>();
			rhs.y = node[1].as<float>();
			rhs.z = node[2].as<float>();
			rhs.w = node[3].as<float>();

			return true;
		}
	};
}

namespace Hazal
{
	YAML::Emitter& operator<<(YAML::Emitter& out, const glm::vec2& vec)
	{
		out << YAML::Flow;
		out << YAML::BeginSeq << vec.x << vec.y << YAML::EndSeq;

		return out;
	}

	YAML::Emitter& operator<<(YAML::Emitter& out, const glm::vec3& vec)
	{
		out << YAML::Flow;
		out << YAML::BeginSeq << vec.x << vec.y << vec.z << YAML::EndSeq;

		return out;
	}

	YAML::Emitter& operator<<(YAML::Emitter& out, const glm::vec4& vec)
	{
		out << YAML::Flow;
		out << YAML::BeginSeq << vec.x << vec.y << vec.z  << vec.w << YAML::EndSeq;

		return out;
	}

	static SRigidBody2DComponent::EBodyType RigidBodyTypeFromString(std::string strType)
	{
		if ("Static" == strType)
		{
			return SRigidBody2DComponent::EBodyType::Static;
		}
		else if ("Dynamic")
		{
			return SRigidBody2DComponent::EBodyType::Dynamic;
		}
		else if ("Kinematic")
		{
			return SRigidBody2DComponent::EBodyType::Kinematic;
		}
		else
		{
			return SRigidBody2DComponent::EBodyType::Static;
		}
	}

	static std::string RigidBodyTypeToString(SRigidBody2DComponent::EBodyType eType)
	{
		switch (eType)
		{
		case SRigidBody2DComponent::EBodyType::Static: return "Static";
		case SRigidBody2DComponent::EBodyType::Dynamic: return "Dynamic";
		case SRigidBody2DComponent::EBodyType::Kinematic: return "Kinematic";
		default: return "Static";
		}
	}

	CSceneSerializer::CSceneSerializer(const Ref<CScene>& rScene)
		: m_pScene(rScene)
	{

	}

	CSceneSerializer::~CSceneSerializer()
	{

	}

	static void SerializeEntity(YAML::Emitter& out, CEntity& entity)
	{
		HZ_CORE_ASSERT(entity.HasComponent<SIDComponent>(), "No Id component!");


		out << YAML::BeginMap;	/// Entity Begin

		out << YAML::Key << "Entity" << YAML::Value << entity.GetUUID();

		if (entity.HasComponent<STagComponent>())
		{
			out << YAML::Key << "TagComponent" << YAML::Value;
			out << YAML::BeginMap;	///< TagComponent Begin

			auto& tag = entity.GetComponent<STagComponent>().m_strTag;
			out << YAML::Key << "Tag" << YAML::Value << tag;

			out << YAML::EndMap;	///< TagComponent End
		}

		if (entity.HasComponent<STransformComponent>())
		{
			out << YAML::Key << "TransformComponent" << YAML::Value;
			out << YAML::BeginMap;

			auto& transform = entity.GetComponent<STransformComponent>();
			out << YAML::Key << "Translation" << YAML::Value << transform.m_vecTranslation;
			out << YAML::Key << "Rotation" << YAML::Value << transform.m_vecRotation;
			out << YAML::Key << "Scale" << YAML::Value << transform.m_vecScale;

			out << YAML::EndMap;
		}

		if (entity.HasComponent<SCameraComponent>())
		{
			out << YAML::Key << "CameraComponent" << YAML::Value;
			out << YAML::BeginMap;	///< Camera Component Begin

			auto& cameraComponent = entity.GetComponent<SCameraComponent>();
			auto& camera = cameraComponent.m_Camera;

			out << YAML::Key << "Camera" << YAML::Value;

			out << YAML::BeginMap;	///< Camera Begin
			out << YAML::Key << "ProjectionType" << YAML::Value << (int)camera.GetProjectionType();
			out << YAML::Key << "PerspectiveFOV" << YAML::Value << camera.GetPerspectiveVerticalFOV();
			out << YAML::Key << "PerspectiveNear" << YAML::Value << camera.GetPerspectiveNearClip();
			out << YAML::Key << "PerspectiveFar" << YAML::Value << camera.GetPerspectiveFarClip();
			out << YAML::Key << "OrthograpicSize" << YAML::Value << camera.GetOrthographicSize();
			out << YAML::Key << "OrthograpicNear" << YAML::Value << camera.GetOrthographicNearClip();
			out << YAML::Key << "OrthograpicFar" << YAML::Value << camera.GetOrthographicFarClip();
			out << YAML::EndMap;	///< Camera End

			out << YAML::Key << "Primary" << YAML::Value << cameraComponent.m_bPrimary;
			out << YAML::Key << "FixedAspectRadio" << YAML::Value << cameraComponent.m_bFixedAspectRadio;

			out << YAML::EndMap;	///< Camera Component End
		}

		if (entity.HasComponent<SSpriteRendererComponent>())
		{
			out << YAML::Key << "SpriteRendererComponent" << YAML::Value;
			out << YAML::BeginMap;	///< Sprite Renderer Component Begin

			auto& spriteRendererComponent = entity.GetComponent<SSpriteRendererComponent>();

			out << YAML::Key << "Color" << YAML::Value << spriteRendererComponent.m_vecColor;

			out << YAML::EndMap;	///< Sprite Renderer Component End
		}

		if (entity.HasComponent<SRigidBody2DComponent>())
		{
			out << YAML::Key << "RigidBody2DComponent" << YAML::Value;
			out << YAML::BeginMap;	///< SRigidBody2D Component Begin

			auto& rigidBodyComponent = entity.GetComponent<SRigidBody2DComponent>();

			out << YAML::Key << "BodyType" << YAML::Value << RigidBodyTypeToString(rigidBodyComponent.m_eBodyType);
			out << YAML::Key << "FixedRotation" << YAML::Value << rigidBodyComponent.m_bFixedRotation;

			out << YAML::EndMap;	///< SRigidBody2D Component End
		}

		if (entity.HasComponent<SCollider2DComponent>())
		{
			out << YAML::Key << "Collider2DComponent" << YAML::Value;
			out << YAML::BeginMap;	///< SRigidBody2D Component Begin

			auto& colliderComponent = entity.GetComponent<SCollider2DComponent>();

			out << YAML::Key << "Offset" << YAML::Value << colliderComponent.m_v2Offset;
			out << YAML::Key << "Size" << YAML::Value << colliderComponent.m_v2Size;
			out << YAML::Key << "Density" << YAML::Value << colliderComponent.m_fDensity;
			out << YAML::Key << "Friction" << YAML::Value << colliderComponent.m_fFriction;
			out << YAML::Key << "Restitution" << YAML::Value << colliderComponent.m_fRestitution;
			out << YAML::Key << "RestitutionThreshold" << YAML::Value << colliderComponent.m_fRestitutionThreshold;

			out << YAML::EndMap;	///< SRigidBody2D Component End
		}

		out << YAML::EndMap;	/// Entity End
	}

	void CSceneSerializer::Serialize(const std::string& strFilePath)
	{
		YAML::Emitter out;
		out << YAML::BeginMap;
		out << YAML::Key << "Scene" << YAML::Value << "Untitled";
		out << YAML::Key << "Entities";
		out << YAML::BeginSeq;
		m_pScene->m_registry.each([&](auto& entityId) 
			{
				CEntity entity = { entityId, m_pScene.get() };
				if (!entity)
				{
					return;
				}

				SerializeEntity(out, entity);
			});
		out << YAML::EndSeq;
		out << YAML::EndMap;

		std::ofstream fOut(strFilePath);
		fOut << out.c_str();
	}

	void CSceneSerializer::SerializeRuntime(const std::string& strFilePath)
	{
		/// No implemented
		HZ_CORE_ASSERT(false, "No implemented!");
	}

	bool CSceneSerializer::Deserialize(const std::string& strFilePath)
	{
		std::ifstream stream(strFilePath);
		std::stringstream strStream;
		strStream << stream.rdbuf();

		YAML::Node data = YAML::Load(strStream.str());
		if (!data["Scene"])
		{
			return false;
		}

		std::string sceneName = data["Scene"].as<std::string>();
		HZ_CORE_ASSERT("Deserializing scene '{0}'", sceneName);

		auto entites = data["Entities"];
		if (entites)
		{
			for (auto entity : entites)
			{
				/// Deserialize id component
				uint64_t uuid = entity["Entity"].as<uint64_t>();

				/// Deserialize tag component
				std::string strTag = "";
				auto tagComponent = entity["TagComponent"];
				if (tagComponent)
				{
					strTag = tagComponent["Tag"].as<std::string>();
				}

				HZ_CORE_ASSERT("Deserialized entity with ID = {0}, tag = {1}", uuid, strTag);

				CEntity deserializedEntity = m_pScene->CreateEntityWithUUID(uuid, strTag);

				/// Deserialize transform component
				auto transformComponent = entity["TransformComponent"];
				if (transformComponent)
				{
					auto& tc = deserializedEntity.GetComponent<STransformComponent>();
					tc.m_vecTranslation = transformComponent["Translation"].as<glm::vec3>();
					tc.m_vecRotation = transformComponent["Rotation"].as<glm::vec3>();
					tc.m_vecScale = transformComponent["Scale"].as<glm::vec3>();
				}

				/// Deserialize camera component
				auto cameraComponent = entity["CameraComponent"];
				if (cameraComponent)
				{
					auto& cc = deserializedEntity.AddComponent<SCameraComponent>();
					auto cameraProps = cameraComponent["Camera"];

					cc.m_Camera.SetProjectionType(cameraProps["ProjectionType"].as<int>());
					cc.m_Camera.SetPerspectiveVerticalFOV(cameraProps["PerspectiveFOV"].as<float>());
					cc.m_Camera.SetPerspectiveNearClip(cameraProps["PerspectiveNear"].as<float>());
					cc.m_Camera.SetPerspectiveFarClip(cameraProps["PerspectiveFar"].as<float>());
					cc.m_Camera.SetOrthographicSize(cameraProps["OrthograpicSize"].as<float>());
					cc.m_Camera.SetOrthographicNearClip(cameraProps["OrthograpicNear"].as<float>());
					cc.m_Camera.SetOrthographicFarClip(cameraProps["OrthograpicFar"].as<float>());

					cc.m_bPrimary = cameraComponent["Primary"].as<bool>();
					cc.m_bFixedAspectRadio = cameraComponent["FixedAspectRadio"].as<bool>();
				}

				/// Deserialize sprite component
				auto spriteRendererComponent = entity["SpriteRendererComponent"];
				if (spriteRendererComponent)
				{
					auto& sc = deserializedEntity.AddComponent<SSpriteRendererComponent>();
					sc.m_vecColor = spriteRendererComponent["Color"].as<glm::vec4>();
				}

				/// Deserialize rigidbody2d component
				auto rigidBodyRendererComponent = entity["RigidBody2DComponent"];
				if (rigidBodyRendererComponent)
				{
					auto& sc = deserializedEntity.AddComponent<SRigidBody2DComponent>();
					sc.m_eBodyType = RigidBodyTypeFromString(rigidBodyRendererComponent["BodyType"].as<std::string>());
					sc.m_bFixedRotation = rigidBodyRendererComponent["FixedRotation"].as<bool>();
				}

				/// Deserialize collider2d component
				auto colliderRendererComponent = entity["Collider2DComponent"];
				if (colliderRendererComponent)
				{
					auto& sc = deserializedEntity.AddComponent<SCollider2DComponent>();
					sc.m_v2Offset = colliderRendererComponent["Offset"].as<glm::vec2>();
					sc.m_v2Size = colliderRendererComponent["Size"].as<glm::vec2>();

					sc.m_fDensity = colliderRendererComponent["Density"].as<float>();
					sc.m_fFriction = colliderRendererComponent["Friction"].as<float>();

					sc.m_fRestitution = colliderRendererComponent["Restitution"].as<float>();
					sc.m_fRestitutionThreshold = colliderRendererComponent["RestitutionThreshold"].as<float>();
				}
			}	
		}

		return true;
	}

	bool CSceneSerializer::DeserializeRuntime(const std::string& strFilePath)
	{
		/// No implemented
		HZ_CORE_ASSERT(false, "No implemented!");
		return false;
	}

}